/**
 *  Copycard 
 *  Author: Wei Zhao	 ,Wei Zhang
 */



import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;


public class Copycard  extends Applet
{
  
  private static boolean init=true;
  private static RandomData rand;  
  
  
 
  final static byte INS_INIT=(byte)0x10;
  
  final static byte INS_SEND_KEY=(byte)0x20;
  final static byte INS_CHECK_PIN=(byte)0x18;
  
  
  final static byte INS_LOAD=(byte)0x22;
  
  // Punkt deleten um Praemie zu tauschen
  final static byte INS_PAY=(byte)0x24;
  
    // Liefern die Kontozustanden zurueck
  final static byte INS_BALANCE=(byte)0x28;
  
  private static OwnerPIN pin= null;
  
  /*
   * Definieren die eigene Dataposition in APDU
   */
  
  //Datapostion in APDU
  final static short OFFSET_DATA= ISO7816.OFFSET_CDATA;
  //Hashposition in APDU
  final static short OFFSET_HASH= ISO7816.OFFSET_CDATA+(short)16;
  
  /*
   * Definieren die eigne Dataposition in APDU-Data
   */
  
  //Datapositon  in APDU-Data
  final static short OFFSET_APDU_DATA=(short)0;
  //Noncepositon  in APDU-Data
  final static short OFFSET_APDU_NONCE=(short)8;
  //Hashposition  in APDU-Data
  final static short OFFSET_APDU_HASH=(short)16;
  
    
  static byte[] keypairs  = {
	      (byte)0x77, (byte)0x60, (byte)0xA5, (byte)0xE2,
	      (byte)0x21, (byte)0x65, (byte)0x06, (byte)0xAE,
	      (byte)0x15, (byte)0x21, (byte)0x80, (byte)0xB4,
	      (byte)0xC9, (byte)0x36, (byte)0x22, (byte)0x25,
	      (byte)0xB4, (byte)0x46, (byte)0xD5, (byte)0x2D,
	      (byte)0x9C, (byte)0x57, (byte)0x56, (byte)0x41
	      };
  final static short CARD_STATUS_UNLOCKED=(short)0;
  
  final static short CARD_STATUS_LOCKED=(short)1;
  
  private short CARD_STATE=(short)1;
  private short value = 0;


  //Speichern als Nonce,,die bei Card  generiet ist
  private byte[] cardNonce=new byte[(short)8];
  ////Speichern als Nonce,die bei Terminal  generiet ist
  private byte[] terminalNonce=new byte[(short)8];
    
  //Speichern die Keys
  private DESKey keys=null;
  private byte[] pins={1,2,3,4};  
  private Cipher cip;
  

    // ------------------------------------------------------------------------
    // Install method,automatisch generiert bei Smartkafee
    // ------------------------------------------------------------------------
    public static void install(byte[] buffer, short offset, byte length)
    {
        new Copycard ();
    }
    // -- END - Public static void install( ... ) -----------------------------

    
    // ------------------------------------------------------------------------
    // Default constructor
    // ------------------------------------------------------------------------
    public Copycard ()
    {
     if (init){
    	//Cipher und Pin wurde initialisiert
       cip=Cipher.getInstance(Cipher.ALG_DES_ECB_NOPAD,false);
       pin= new OwnerPIN((byte)5,(byte)4);
       pin.update(pins,(short)0,(byte)4);
       keys=(DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES,KeyBuilder.LENGTH_DES3_3KEY,false);
       keys.setKey(keypairs,(short) 0);
       rand = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
       init = false;
     }
        register();
    }
    // -- END - public LoyaltyApplet() ----------------------------------------------

    
    // ------------------------------------------------------------------------
    // Process method
    // ------------------------------------------------------------------------
    
    /**
     * Die verschiedene Befehl impliementieren
     * @param apdu  Die eingegebene APDU
     * @author  
     */
    public void process(APDU apdu) throws ISOException
    {

     
     switch (apdu.getBuffer()[ISO7816.OFFSET_INS]) 
     {
       
        case INS_INIT: initTransmission(apdu) ; return;
     
        case INS_LOAD:  load(apdu); return;
       
        case INS_PAY: pay(apdu); return;
       
        case INS_BALANCE:balance(apdu); return;
       
        case INS_SEND_KEY:sendKey(apdu); return;
        
        case INS_CHECK_PIN :checkPin(apdu);return;
     
        case ISO7816.INS_SELECT: return;       
        default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);

    } 
 }
 
 private void checkPin(APDU apdu) {
		
	 
	 byte[] tmpApduData=new byte[(short)2];
	 apdu.setIncomingAndReceive();
	 byte[] bufferPin = {0,0,0,0};
	 
	 Util.arrayCopy(apdu.getBuffer(),ISO7816.OFFSET_CDATA,bufferPin,(short)0,(short)4);
	
	 if (pin.check(bufferPin,(short)0,(byte)4))	{ 
		 
		 CARD_STATE=CARD_STATUS_UNLOCKED;
		 Util.setShort(tmpApduData,(short)0,(short)2); // passwort war unguentig ,4 zureuckliefert
		 sendAPDU(tmpApduData,apdu,(short)2);	
				 
	  }else {
		  CARD_STATE = CARD_STATUS_LOCKED;
		  Util.setShort(tmpApduData,(short)0,(short)4);
		  sendAPDU(tmpApduData,apdu,(short)2);		  
	  }	 
	 
	}
 
/**
  * Initieren die Key.Die 
  * Die eingegebne APDU,darin die Key liegt ist ,mit Klartext schickt werden.
  * @apdu Eingegebene APDU
  * @author  
  */
    
  public void sendKey(APDU apdu)
  {
     
 //   keys=(DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES,KeyBuilder.LENGTH_DES3_3KEY,false);
	  apdu.setIncomingAndReceive();
	  if (getCardState()==CARD_STATUS_UNLOCKED){
				sendAPDU(keypairs,apdu,(short)24);
	  }else  ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);       
       
  }
    
 /**
  * Die Funktion kannt nicht direkt von User angeruft werden,sonden nur von deleteFunktion angeruft werden
  * @param apdu Die eingegebne APDU
  * @author  
  */
  void balance(APDU apdu)
  {
	  if (getCardState()==CARD_STATUS_UNLOCKED){
	  //Speichern die 8 Byte Data und 8 Byte terminalNonce
     byte[] apduData = new byte[16];
     //SPeichern die 32 Byte APDUData, 8 Byte Data ,8 Byte Terminalnonce , 16 Byte hashwert von cardNonce
    byte[] tmpApduData = new byte[32];
    byte[] tmpHash;
    byte[] apduBuffer = apdu.getBuffer();
    byte[] klarText;

    apdu.setIncomingAndReceive();

    Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA,apduData,(short)0,(short)16);
    
    //Generieren die Hashwert um die eingegebene Hashwert zu ueberpruefen
    tmpHash=hashNonce(cardNonce);
      
    //Vergleichen die eingegebene Hashwert mit die generiert Hashwert
    //Wenn die beide Hashwert nicht identische sind,eine Exception passiert
    if(Util.arrayCompare(tmpHash,(short)0,apduBuffer,OFFSET_HASH,(short)16)!=(byte)0)
       ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    
    //Entschluessen die eingegebene Data
     klarText=decrypto(apduData,(short)16);
      
    //Speichern die terminalNonce
     Util.arrayCopy(klarText,(short)8,terminalNonce,(short)0,(short)8);
   
      Util.setShort(apduData,(short)2,value);
   
     //Generieren neue cardNonce 
    cardNonce=newNonce();
    
    //Copy die neue cardNonce um Schicken zu vorbereiten
    Util.arrayCopy(cardNonce,(short)0,apduData,(short)8,(short)8);
       
    //Verschluessen die Data mit entsprechicht Key
      apduData=encrypto(apduData,(short)16);
    
      //Berechen die Hashwert mit terminalNonce
        tmpHash=hashNonce(terminalNonce);
   
        Util.arrayCopy(apduData,(short)0,tmpApduData,OFFSET_APDU_DATA,(short)16);
       Util.arrayCopy(tmpHash,(short)0,tmpApduData,OFFSET_APDU_HASH,(short)16);
    
       //Schicken das Ergebnis
       sendAPDU(tmpApduData,apdu,(short)32);
	  }
	  else  ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);   
  }
 
/**
 * Addieren die Punkten auf Kontozustand
 * @param apdu Eingegebene APDU
 * @author  
 */
 void load(APDU apdu)
  {
	  if (getCardState()==CARD_STATUS_UNLOCKED){
	//Speichern die 8 Byte Data + 8 Byte terminalNonce
    byte[] apduData=new byte[(short)16];
    //Speichern die 32 Byte APDUData, 8 Byte Data und 8 Byte Terminalnonce und 16 Byte Hashwert von cardNonce
    byte[] tmpApduData=new byte[(short)32];
    
    byte[] tmpHash;
    //Speichern die APDU
    byte[] apduBuffer= apdu.getBuffer();
    byte[] klarText;
    //SPeicher die Kontozustand
    short val = 0;
    apdu.setIncomingAndReceive();
    Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA,apduData,(short)0,(short)16);
     //Generieren die Hashwert um die eingegebene Hashwert zu ueberpruefen
     tmpHash=hashNonce(cardNonce);
        
     //Vergleichen die eingegebene Hashwert mit die generiert Hashwert
      //Wenn die beide Hashwert nicht identische sind,eine Exception passiert
      if(Util.arrayCompare(tmpHash,(short)0,apduBuffer,OFFSET_HASH,(short)16)!=(byte)0)
         ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    
    //Entschluessen die eingegebene Data
      klarText=decrypto(apduData,(short)16);
    //Speichern die terminalNonce
     Util.arrayCopy(klarText,(short)8,terminalNonce,(short)0,(short)8);
    
      val = Util.getShort(apduBuffer, ISO7816.OFFSET_CDATA);
     //Addieren die Punktanzahl 
      
     if (val <= 0 || (short)(val + value) > (short)(50000&0x7fff ))
        ISOException.throwIt(ISO7816.SW_DATA_INVALID);
     
     value += val;
  
     //Generieren neue Nonce
     cardNonce=newNonce();
      
     //Speichern die neue Nonce zu apduData zu schiken
      Util.arrayCopy(cardNonce,(short)0,apduData,(short)8,(short)8);
       
      //Verschluessen die apduData
      apduData=encrypto(apduData,(short)16);
    
      //Generieren neue Hashwert
        tmpHash=hashNonce(terminalNonce);
   
       Util.arrayCopy(apduData,(short)0,tmpApduData,OFFSET_APDU_DATA,(short)16);
       Util.arrayCopy(tmpHash,(short)0,tmpApduData,OFFSET_APDU_HASH,(short)16);
    
       sendAPDU(tmpApduData,apdu,(short)32);
       
  }   else  ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);  
  }

 /**
  * Initieren eine neue Transmission
  * Setzen die Kartzustand um die Kart zu besetzen und Kundennummer fuer entspricht Konto
  * @param apdu Die eingegebene APDU
  * author  
  */
 
 void initTransmission(APDU apdu)
  {
	  //Speichern fuer Hashwert
      byte[] tmpHash;
      //Speichern die 16 Byte Cipher
      byte[] tmpCipher=new byte[(short)16];
      //Speichern die 32 Byte Apdudata
      byte[] tmpAPDUData=new byte[(short)32];
        
      //Setze die Applet Arbeitmodel
      apdu.setIncomingAndReceive();
    
      //Wenn die Kart frei ist,kann man die benutzen
      if(getCardState()== CARD_STATUS_UNLOCKED)
       {
    	    	//Hole die Cipher ab
    	  	Util.arrayCopy(apdu.getBuffer(), ISO7816.OFFSET_CDATA,tmpCipher,(short)0,(short)16); 
    
    	  	//Entschluessen die eingegebene Data
    	  	tmpCipher=decrypto(tmpCipher,(short)16);
    
    	  	//Hole die terminalNonce ab
    	  	Util.arrayCopy(tmpCipher,(short)8,terminalNonce,(short)0,(short)8);
    	  	//Generieren die neue cardNonce
    	  	cardNonce=newNonce();
      
    	  	//Speichern die cardNonce zu schiken
    	  	Util.arrayCopy(cardNonce,(short)0,tmpCipher,(short)8,(short)8);
    	  	//Verschluessen die Klartext 
    	  	tmpCipher=encrypto(tmpCipher,(short)16);
    	  	//Berechen die neu Hashwert mit terminalNonce
    	  	tmpHash=hashNonce(terminalNonce);
   
    	  	Util.arrayCopy(tmpCipher,(short)0,tmpAPDUData,OFFSET_APDU_DATA,(short)16);
    	  	Util.arrayCopy(tmpHash,(short)0,tmpAPDUData,OFFSET_APDU_HASH,(short)16);
    	  	
    	  	//Schiken das Ergebnis
    	  	sendAPDU(tmpAPDUData,apdu,(short)32);
    	  	
       }else ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);  
    	   
  }

  /**
   * Loeschen die Punkten auf entspricht Konto
   * @param apdu Die eingegebene APDU
   * @author  
   */
  void pay(APDU apdu)
  { 
	  if (getCardState()==CARD_STATUS_UNLOCKED){
	    //Speichern die 8 Byte Data+ 8 Byte terminalNonce
	 
	  	byte[] apduData=new byte[(short)16];
	    //SPeichern die 32 Byte APDUData, 
	  	//8 Byte Data+8 Byte Terminalnonce+16 Byte durch cardNonce generierte Hashwert
	  	byte[] tmpApduData=new byte[(short)32];
	  	byte[] tmpHash;
	  	byte[] apduBuffer=apdu.getBuffer();
	  	byte[] klarText;
	        
	  	apdu.setIncomingAndReceive();

	  	//Hole die Cipher ab
	  	Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA,apduData,(short)0,(short)16);
	     
        //Generieren die Hashwert mit cardNonce um mit die eingegebene Hashwert zu vergleichen 
	  	tmpHash=hashNonce(cardNonce);
      
	  	 
	    //Vergleichen die eingegebene Hashwert mit die generiert Hashwert
	    //Wenn die beide Hashwert nicht identische sind,eine Exception passiert
	  	if(Util.arrayCompare(tmpHash,(short)0,apduBuffer,OFFSET_HASH,(short)16)!=(byte)0)
	  		ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
     
	  	//Entschluessen die Cipher
     	klarText=decrypto(apduData,(short)16);
     	//Hole die eingegebene terminalNonce
     	Util.arrayCopy( klarText,(short)8,terminalNonce,(short)0,(short)8);
    
     
        short val = Util.getShort(apduBuffer, ISO7816.OFFSET_CDATA);
        
        if (val <= 0 || (short)(value - val) < 0)
        ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        value -= val;
     
        //Generieren neue cardNonce
     	cardNonce=newNonce();
     	//Fuegen die neue cardNonce hinzu
        Util.arrayCopy(cardNonce,(short)0,apduData,(short)8,(short)8);
        //Verschluessen das Ergebnis
        apduData=encrypto(apduData,(short)16);
    
        //Fuegen die Hashwert hinzu
        tmpHash=hashNonce(terminalNonce);
   
        Util.arrayCopy(apduData,(short)0,tmpApduData,OFFSET_APDU_DATA,(short)16);
        Util.arrayCopy(tmpHash,(short)0,tmpApduData,OFFSET_APDU_HASH,(short)16);
         //Schiken das generiert Ergebnis
        sendAPDU(tmpApduData,apdu,(short)32);   
  }
        else  ISOException.throwIt( ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);  
  }

  /**
   * Berechen die neue cardNonce mit random
   * @return byte[] Das neue 8-Byte cardNonce
   * @author  
   */
  public byte [] newNonce()
 {
	 //Speichern die neue cardNonce
       byte[] tmpNonce=new byte[(short)8];
   
    
     rand.generateData(tmpNonce,(short)0,(short)8);
   
    return tmpNonce;
 }
  
/**
 * Berechen die Hashwert mit terminalNonce oder cardNonce
 * @param toHash Die Bytearray zu hash
 * @return byte[] Das neue 16-Byte Hashwert
 * @author  
 */  
 public byte[] hashNonce(byte[] toHash)
 {
     MessageDigest md;
     //Um die 16-Byte Hashwert zu speichern
     byte[] tmpHash=new byte[(short)16];
      
      md = MessageDigest.getInstance(MessageDigest.ALG_MD5, false);
      //Generieren die 16-Byte Hashwert
      md.doFinal(toHash,(short)0,(short)8,tmpHash,(short)0);
    
       return tmpHash;
 }

/**
 * Hole die Kartzustand ab
 * @return short Kartzustand,0 ==>Kart ist frei,1==>Kart ist besetzt
 * @author  
 */
  private  short getCardState()
  {
      return CARD_STATE;
  }
  
  /**
   * Setzen die Kartzustand
   * @param cardState Die neue Kartzustand
   * @author  
   */
  private void setCardState(short cardState)
  {
      CARD_STATE=cardState;   
  }
  
  /**
   * Entschluessen die eingegebene Data
   * @param userID  
   * @param toDec  
   * @param dataLength 
   * @return Klartext
   * @author  
   */
 public byte[] decrypto(byte[] toDec,short dataLength)
  {
      byte[] tmpCipher=new byte[dataLength];
    
       cip.init(keys, Cipher.MODE_DECRYPT);
    //Entschluessen die Cipher 
    cip.doFinal(toDec,(short)0,dataLength,tmpCipher,(short)0);
  
      return tmpCipher;
  }
  
 /**
  * Verschluessen die eingegebene Data
  * @param userID  
  * @param toEnc  
  * @param dataLength
  * @return Klartext
  * @author  
  */  
  public byte[] encrypto(byte[] toEnc,short dataLength)
  {
    
    byte[] tmpCipher=new byte[dataLength];    
    //Init
    cip.init(keys, Cipher.MODE_ENCRYPT);
    //Verschluessen die Klartext 
    cip.doFinal(toEnc,(short)0,dataLength,tmpCipher,(short)0);
  
      return tmpCipher;
  }
 
  
  
 /**
  * Schiken das Ergebnis
  * @param tmpAPDUData Das Ergebnis zu schiken
  * @param apdu
  *  author Wei Zhao  
  */ 
  void sendAPDU(byte[] tmpAPDUData,APDU apdu,short len)
  {
	  
        Util.arrayCopy(tmpAPDUData,(short)0,apdu.getBuffer(), ISO7816.OFFSET_CDATA,len);
        apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
  }
  

}

  
  



